package pers.cz.utils;

import pers.cz.mapper.*;
import pers.cz.model.*;
import pers.cz.spring.SpringContextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.script.ScriptException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * 初始化分表规则上下文
 * @program: PostGirl-panent
 * @description: PartitionContextBuilder
 * @author: Cheng Zhi
 * @create: 2022-04-20 20:43
 **/
public class PartitionContextBuilder {

    Logger logger = LoggerFactory.getLogger(PartitionContextBuilder.class);
    private static final String LUA_CALL_SCRIPT="return partition_rule(table_partition_params)";

    // 存放所有的schemas
    Map<Integer, SystemPartitionScheme> schemas = new HashMap<Integer,SystemPartitionScheme>();
    // 存放分表常量参数
    Map<String, SystemPartitionConstParam> consts = new HashMap<String,SystemPartitionConstParam>();
    // 存放分表逻辑
    Map<String, SystemPartitionLogicParam> logics = new HashMap<String,SystemPartitionLogicParam>();
    // 存放所有的路由参数
    Map<String,Map<String, SystemPartitionRelation>> partitionParams = new HashMap<String,Map<String,SystemPartitionRelation>>();

    public PartitionContext build() throws ScriptException, IOException {

        boolean success = loadSchemas();
        if (!success) {
            return null;
        }

        PartitionContext partitionContext = new PartitionContext();
        // 拼接成完整的lua 查询System_partition_rules
        List<SystemPartitionRule> systemPartitionRuleList = SpringContextUtil.getBean(SystemPartitionRuleMapper.class).findAll();
        for (SystemPartitionRule systemPartitionRule : systemPartitionRuleList) {
            String tableName = systemPartitionRule.getTableName();
            Integer schemeId = systemPartitionRule.getSchemeId();

            SystemPartitionScheme systemPartitionScheme = schemas.get(schemeId);
            // 解析lua伪代码
            Map<String, String> constParams = new TreeMap<>();

            String luaScript = analyze(systemPartitionScheme, tableName, constParams);

            logger.debug("加载脚本内容：" + luaScript);
            Map<String, SystemPartitionRelation> partitionRelationMap = partitionParams.get(tableName);
            partitionContext.addConfig(schemeId,tableName, luaScript, constParams, partitionRelationMap);

        }
        return partitionContext;

    }

    /**
     * 解析SystemPartitionScheme表中的lua,将lua中的占位字段替换为真实字段
     * @param schema      SystemPartitionScheme表
     * @param tableName   表名
     * @param constParams 变量集
     * @return
     */
    private String analyze(SystemPartitionScheme schema,String tableName, Map<String, String> constParams) throws IOException {

        String luaScript = schema.getLuaContext();
        // 按照行解析
        int n;
        while ((n=luaScript.indexOf("?{"))>-1) {

            int m = luaScript.indexOf("}",n);
            if (n > -1 && m > -1) {
                String paramName = luaScript.substring(n+2, m);

                // 根据paramName查找paramValue  find paramValue by paramName
                String paramValue = getParamValue(paramName, tableName, constParams);
                // 做替换 replace paramName use paramValue

                StringBuilder sb = new StringBuilder(luaScript.length() + 16);
                sb.append(luaScript.substring(0, n));
                sb.append(paramValue);
                sb.append(luaScript.substring(m+1));
                luaScript = sb.toString();
            }

        }

        BufferedReader reader = new BufferedReader(new StringReader(luaScript));
        StringBuilder sb = new StringBuilder(luaScript.length());
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                // line不包含require 或者 包含require但不是开头，则认为是需要append的行
                if (line.indexOf("require ") == -1 || !line.trim().startsWith("require")) {
                    sb.append(line).append('\n');
                }
            }
            // 加上调用函数脚本，以便调用上下文和函数本身的上下文保持一致，从而避免多线程调用的相关问题
            sb.append(LUA_CALL_SCRIPT).append("\n");
            return sb.toString();
        } finally {
            reader.close();
        }

    }

    /**
     * 根据paramName获取paramValue
     * @param paramName     参数名
     * @param tableName     表名
     * @param constParams   保存paramName和paramValue的映射关系
     * @return
     */
    private String getParamValue(String paramName, String tableName, Map<String, String> constParams) {

        if("TABLE_NAME".equals(paramName)){
            return tableName;
        }

        String key = tableName + "|" + paramName;
        SystemPartitionConstParam systemPartitionConstParam = consts.get(key);  // 所有的表规则参数
        SystemPartitionLogicParam systemPartitionLogicParam = logics.get(key);  // 所有的库路由参数

        if (systemPartitionConstParam != null && systemPartitionLogicParam != null) {
            // 如果都不为空，说明库路由和表规则参数名重复
            throw new IllegalArgumentException("Invalid Configuration: there are duplicate const Param and logic param :" + paramName);
        } else if (systemPartitionConstParam != null) {
            String constParamValue = systemPartitionConstParam.getParamValue();
            constParams.put(paramName, constParamValue);
            return constParamValue;
        } else if (systemPartitionLogicParam != null) {
            // here is a piece of code 这里获取到的是一段代码
            String logicParamValue = systemPartitionLogicParam.getParamValue();
            return logicParamValue;
        } else {

            throw new IllegalArgumentException("the param " + paramName + " for " + tableName + "is not found !");
        }

    }

    /**
     * 加载路由表
     * @return
     */
    private boolean loadSchemas() {

        boolean success = true;

        try {
            // 查询schema
            List<SystemPartitionScheme> systemPartitionSchemeList = SpringContextUtil.getBean(SystemPartitionSchemeMapper.class).findAll();
            for (SystemPartitionScheme systemPartitionScheme : systemPartitionSchemeList) {
                schemas.put(systemPartitionScheme.getSchemeId(), systemPartitionScheme);
            }

            // 加载分表规则
            List<SystemPartitionConstParam> systemPartitionConstParamList = SpringContextUtil.getBean(SystemPartitionConstParamMapper.class).findAll();
            for (SystemPartitionConstParam systemPartitionConstParam : systemPartitionConstParamList) {
                consts.put(systemPartitionConstParam.getBaseName() + "|" + systemPartitionConstParam.getParamName(), systemPartitionConstParam);
            }

            // 加载logics
            List<SystemPartitionLogicParam> systemPartitionLogicParamList = SpringContextUtil.getBean(SystemPartitionLogicParamMapper.class).findAll();
            for (SystemPartitionLogicParam systemPartitionLogicParam : systemPartitionLogicParamList) {
                logics.put(systemPartitionLogicParam.getTableNaem() + "|" + systemPartitionLogicParam.getParamName(), systemPartitionLogicParam);
            }

            List<SystemPartitionRelation> systemPartitionRelationList = SpringContextUtil.getBean(SystemPartitionRelationMapper.class).findAll();
            for (SystemPartitionRelation systemPartitionRelation : systemPartitionRelationList) {
                String tableBaseName = systemPartitionRelation.getTableName();
                Map<String,SystemPartitionRelation> partitionParam=partitionParams.get(tableBaseName);
                if(partitionParam == null){
                    partitionParam=new HashMap<String,SystemPartitionRelation>();
                    partitionParams.put(tableBaseName, partitionParam);
                }
                //所有的参数都提供
                //参数名为Key(转为大写),Lua参数名为Value的map
                partitionParam.put(systemPartitionRelation.getParamName(), systemPartitionRelation);
            }
        } catch (Exception e) {
            success = false;
            throw new IllegalArgumentException("load partition rules fail..." + e);
        }

        return success;
    }

    /**
     * 获取参数集合
     * @return
     */
    public Map<String, SystemPartitionConstParam> getConsts() {
        return consts;
    }

    public static void main(String[] args) {
        String test = "this is a ?{name} unit";
        int n = test.indexOf("?{");
        int index = test.indexOf("}");
        Map map = new HashMap();
        map.put("name", "cz");
        StringBuilder sb = new StringBuilder(test.length() + 16);
        sb.append(test.substring(0, n));
        String name = test.substring(n+2, index);
        sb.append(map.get(name));
        sb.append(test.substring(index+1));
        System.out.println(sb.toString());
    }
}
